﻿using System.Data;
using Crucible.DocSearch;
using Crucible.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Script.Serialization;
using WebMatrix.WebData;

namespace Crucible.Controllers
{
    public static class AssertionHelper
    {
        public static string FriendlyDate(DateTime dt)
        {
            var ago = (DateTime.Now - dt);

            if (ago.TotalSeconds < 60)
            {
                return (int) ago.TotalSeconds + " s ago";
            }

            if (ago.TotalMinutes < 60)
            {
                return (int) ago.TotalMinutes + " min ago";
            }

            if (dt.Date == DateTime.Today)
            {
                return dt.ToString("h:mm t");
            }

            if (dt.Year == DateTime.Today.Year)
            {
                return dt.ToString("MMM d h:mm t");
            }

            return dt.ToString("MMM d 'yy h:mm t");
        }

        public static string GetTreeJson(int id, int depth)
        {
            return GetJson(GetTreeObject(id, depth));
        }

        public static string GetJson(object o)
        {
            return new JavaScriptSerializer().Serialize(o);
        }

        public static object GetTreeObject(int id, int depth)
        {
            using (var context = new ArgumentEntities())
            {
                var nodeResult = context.USP_GetArgument(id, WebSecurity.CurrentUserId, depth);
                var nodes = nodeResult.ToList();

                var edgeResult = nodeResult.GetNextResult<GetArgument_Edges>();
                var edges = edgeResult.ToList();

                var argConn = new ArgumentConnection();
                var root = argConn.Nodes.FirstOrDefault(a => a.Id == id);

                var rootParents = from link in argConn.NodeLinks
                                  join par in argConn.Nodes on link.Parent_Id equals par.Id
                                  where link.Child_Id == id
                                  select par;

                return new { root, nodes, edges, rootParents };

            }
        }

        public static List<PublishProblem> TryPublish(int id)
        {
            List<GetArgument_Nodes> nodes;
            List<GetArgument_Edges> edges;

            using (var context = new ArgumentEntities())
            {
                var nodeResult = context.USP_GetArgument(id, WebSecurity.CurrentUserId, -1);
                nodes = nodeResult.ToList();

                var edgeResult = nodeResult.GetNextResult<GetArgument_Edges>();
                edges = edgeResult.ToList();
            }


            var gNodes = BuildGraph(id, nodes, edges);

            var messages = (from gn in gNodes
                            from m in gn.Messages
                            select new PublishProblem() { Id = gn.Node.Id, Message = m }).ToList();

            if (messages.Any())
            {
                return messages;
            }
            
            // Green light, publish everything

            using (var db = new ArgumentConnection())
            {
                foreach (GraphNode gn in gNodes)
                {
                    var existing = db.Nodes.Find(gn.Node.Id);
                    if (existing != null)
                    {
                        existing.Published = true;
                        db.Entry(existing).State = EntityState.Modified;

                        IndexManager.EditAssertion(existing, WebSecurity.CurrentUserName);
                    }
                }

                db.SaveChanges();
            }

            return messages;

        }

        private static List<GraphNode> BuildGraph(int id, List<GetArgument_Nodes> nodes, List<GetArgument_Edges> edges)
        {
            var list = new List<GraphNode>();
            var root = new GraphNode(nodes.First(n => n.Id == id));
            list.Add(root);

            var openSet = new List<GraphNode> {root};

            while (openSet.Any())
            {
                var open = openSet[0];
                openSet.RemoveAt(0);
                list.Add(open);

                var openEdges = edges.Where(ed => ed.ParentId == open.Node.Id).ToList();
                if (openEdges.Any())
                {
                    foreach (var e in openEdges)
                    {
                        var infLoop = false;
                        if (list.Any(n => n.Node.Id == e.ChildId))
                        {
                            // check the parentage for infinite loops
                            var current = open;
                            while (current != null)
                            {
                                if (current.Node.Id == e.ChildId)
                                {
                                    // if so, break the foreach and add a message to open.
                                    open.Messages.Add(String.Format("Node {0} ({1}) has an infinite cycle.", open.Node.Title, open.Node.Id));
                                    infLoop = true;
                                    break;
                                }
                                current = current.Parent;
                            }
                        }

                        if (!infLoop)
                        {
                            var child = new GraphNode(nodes.First(n => n.Id == e.ChildId));
                            open.Children.Add(child);
                            child.Parent = open;
                            openSet.Add(child);
                        }

                    }
                }
                else
                {
                    if (open.Node.Type == (int)NodeType.Assertion || open.Node.Type == (int)NodeType.Interpretation)
                    {
                        open.Messages.Add(String.Format("Node {0} ({1}) cannot be published without evidence.", open.Node.Title, open.Node.Id));
                    }
                }

                if (string.IsNullOrEmpty(open.Node.Title))
                {
                    open.Messages.Add(String.Format("Node {0} (under {1}) cannot be published without a title.", open.Node.Id, open.Parent.Node.Title));
                }

            }

            return list;

        }

        public static IEnumerable<NodeLink> GetLinks(int parentId, string text)
        {
            var links = new List<NodeLink>();

            if (text == null) return links;

            var r = new Regex("\\{\\{\\[([0-9]+)\\]((?!\\}\\}).)+\\}\\}"); // {{[3]Hello world}}

            MatchCollection matches = r.Matches(text);

            foreach (Match m in matches)
            {
                int childId;

                if (!int.TryParse(m.Groups[1].Value, out childId)) continue; // The id is not an integer, so skip

                if (childId != parentId && links.All(lk => lk.Child_Id != childId)) // Don't allow duplicates or self-references
                {
                    var link = new NodeLink() { Parent_Id = parentId, Child_Id = childId };
                    links.Add(link);
                }
            }
            

            return links;

        }

        private class GraphNode
        {
            public GetArgument_Nodes Node;
            public GraphNode Parent;
            public List<GraphNode> Children = new List<GraphNode>();
            public List<string> Messages = new List<string>();

            public GraphNode(GetArgument_Nodes node)
            {
                Node = node;
            }
        }

        public class PublishProblem
        {
            public int Id;
            public string Message;
        }

    }
}